<?php
namespace app\common\library;
use think\Cache,
    think\Db;
/*
 * 中奖处理类
 * 未做浮点数处理
 *
 * */
class Lottery
{
    //综合中奖率
    private $integrated_odds=0.04;
    //初始中奖区间
    private $win_section=[
        [
            'start'=>0, // 起始区间(包含)
            'end'=>0.1,   // 结束区间(包含)
            'odds'=>0.1,   // 区间中奖比率*总中奖比率
        ],
        [
            'start'=>0.11, // 起始区间(包含)
            'end'=>0.2,   // 结束区间(包含)
            'odds'=>0.1,   // 区间中奖比率*总中奖比率
        ],
        [
            'start'=>0.21,
            'end'=>0.3,
            'odds'=>0.1,
        ],
        [
            'start'=>0.31,
            'end'=>0.4,
            'odds'=>0.1,
        ],
        [
            'start'=>0.41,
            'end'=>0.5,
            'odds'=>0.1,
        ],
        [
            'start'=>0.51,
            'end'=>0.6,
            'odds'=>0.1,
        ],
        [
            'start'=>0.61,
            'end'=>0.7,
            'odds'=>0.1,
        ],
        [
            'start'=>0.7,
            'end'=>0.8,
            'odds'=>0.1,
        ],
        [
            'start'=>0.8,
            'end'=>0.9,
            'odds'=>0.1,
        ],
        [
            'start'=>0.9,
            'end'=>1,
            'odds'=>0.1,
        ]
    ];

    //奖池容量 100倍数
    private $list_len=1000;
    //游戏名称
    private $game;
    //当前所在奖池队列位置
    private $list_addr;
    //死循环保护(DEBUG)
    private $error_num=1;
    /*
     * $game:游戏名称,作为识别码
     * */
    public function __construct($game,$odds=1)
    {
        $this->game=$game;
        if(is_array(config('win_section'))){
            $this->win_section=config('win_section');
        }
        if(config('win_list_len')){
            $this->list_len=config('win_list_len');
        }
        $integrated_odds=Db::name('game')->where('id',$game)->value('win_probability');
        if($integrated_odds){
            $this->integrated_odds=$integrated_odds;
        }
        $this->integrated_odds=$this->integrated_odds*$odds;

        $this->checkOdds();
        config('cache',[
            // 驱动方式
            'type'   => 'Redis',
            // 缓存保存目录
            'path'   => CACHE_PATH,
            // 缓存前缀
            'prefix' => '',
            // 缓存有效期 0表示永久缓存
            'expire' => 0,
        ]);
    }
    //获取中奖结果
    public function getResult(){

        $win_section_index=$this->isInWinSection();
        if($win_section_index === false){return false;}
        $InWinSection=$this->win_section[$win_section_index];

        if($InWinSection === false){
            return false;
        }else{
            $key=$this->game;
            //区间已中奖人数
            $num_ready=Cache::get($key.'_win') - $this->winNumPrev($win_section_index);

            //区间应中奖人数
            $num_plan=$InWinSection['odds']*$this->list_len * $this->integrated_odds;

//            奖池已中奖人数
//            $num_ready_all=Cache::get($key.'_win')?Cache::get($key.'_win'):0;
//            echo '<br>奖池已中奖人数：'.$num_ready_all;
//            奖池应中奖人数
//            $num_plan_all=$this->list_len * $this->integrated_odds;
//            echo '<br>奖池应中奖人数：'.$num_plan_all;

            //获取区间剩余人数(包含当前)
            $num_else=$InWinSection['end'] * $this->list_len - $this->list_addr + 1;

            if($num_ready>=$num_plan){
                //中奖名额已经用完，判断不中奖
                return false;
            }elseif($num_else<= $num_plan - $num_ready){
                //应中奖人数>=剩余中奖人数，判断中奖
                Cache::inc($key.'_win');
                return true;
            }

            //其余情况用随机数判断中奖
            $number=mt_rand(1,$this->list_addr);
            if($number <= round($InWinSection['odds']*$this->list_addr)){
                Cache::inc($key.'_win');
                return true;
            }else{
                return false;
            }
        }
    }
    //检查总中奖人数 是否超出区间总人数
    private function checkOdds(){
        $integrated_odds=$this->integrated_odds > 1 ? 1 : $this->integrated_odds;

        $odds_all=$integrated_odds * $this->list_len;
        $odds_section=0;
        foreach($this->win_section as $v){
            $odds_section += ($v['end']-$v['start']) * $this->list_len + 1;
        }
        if($odds_all >= $odds_section){
            //超出则延伸区间
            $this->addSection($odds_all - $odds_section);
        }
    }
    //单位区间按人数1向外延伸
    private function addSection($over_num){
        //转为整型
        $over_num=round($over_num);
        //延伸单位值
        $add_num=1;
        $win_section=$this->win_section;
        foreach($win_section as $k=>&$v){
            $prev_end = $k == 0 ? 1 : round($win_section[$k-1]['end'] * $this->list_len + 1);//上一段的结束点
            $next_start =$k ==
            (count($win_section) - 1) ? $this->list_len : round($win_section[$k+1]['start'] * $this->list_len - 1);
            //需延伸值是否小于单位值
            if($over_num >= 0 && $over_num <= $add_num){
                if(round($v['start'] * $this->list_len - $prev_end) >= $over_num){
                    //片段前方足够延伸结束
                    $v['start'] -= $over_num / $this->list_len;
                    $over_num = 0;
                }elseif(round($next_start - $v['end'] * $this->list_len) >= $over_num){
                    //片段前方不足够延伸结束 后方足够延伸
                    $v['end'] += $over_num / $this->list_len;
                    $over_num = 0;
                }
                //延伸结束跳出循环 否则 判断下一片段
                if($over_num == 0){
                    break;
                }else{
                    continue;
                }

            }else{
                //需延伸值大于单位值
                //前方够延伸单位值
                if(round($v['start'] * $this->list_len - $prev_end) >= $add_num){
                    //片段前方足够延伸结束
                    $v['start'] -= $add_num / $this->list_len;
                    $over_num -= $add_num;
                }
                //判断剩余延伸值是否小于单位值
                if($over_num > 0 && $over_num <= $add_num){
                    if(round($next_start - $v['end'] * $this->list_len) >= $over_num){
                        $v['end'] += $over_num / $this->list_len;
                        $over_num = 0;
                        break;
                    }else{
                        continue;//判断下一片段
                    }
                }else{
                    if(round($next_start - $v['end'] * $this->list_len) >= $add_num){
                        $v['end'] += $add_num / $this->list_len;
                        $over_num -= $add_num;
                    }
                    continue;//判断下一片段
                }
            }

        }
        unset($v);
        $this->win_section=$win_section;
        $error_num=$this->error_num;
        if($over_num > 0){
            $error_num++;
            if($error_num<=10000){
                $this->error_num=$error_num;
                $this->addSection(round($over_num));
            }

        }
    }
    //获取之前区间已中奖人数(超出取区间人数)
    private function winNumPrev($win_section_index)
    {
        $re_odds=0;
        foreach($this->win_section as $k=>$v){
            if($k<$win_section_index){
                $num_odds=$v['odds']*$this->list_len*$this->integrated_odds;
                $num_real=$v['end'] * $this->list_len - $v['start'] * $this->list_len + 1;
                $re_odds+=$num_odds<$num_real?$num_odds:$num_real;
            }
        }
        return $re_odds;
    }
    //判断用户是否在中奖区间的方法
    private function isInWinSection()
    {
        //加入队列，并返回所在队列位置
        $list_num=$this->addCache();
//        dump($list_num);
        //队列总长
        $list_len=$this->list_len;
        //所在队列比例
        $list_percent=$list_num/$list_len;
        //中奖区间
        $win_section=$this->win_section;
        $re=false;
        foreach($win_section as $k=>$v){
            if($list_percent >= $v['start'] && $list_percent <= $v['end']){
                $re=$k;
                break;
            }
        }
//        dump($re);
        return $re;
    }
    //增加奖池人数并返回当前所在位置
    private function addCache()
    {
        $key=$this->game;
        Cache::inc('game_'.$key);
        $handler=Cache::get('game_'.$key);
        if($handler>$this->list_len){
            //删除队列
            Cache::rm('game_'.$key);
            //删除队列中奖人数
            Cache::rm($key.'_win');
            Cache::inc('game_'.$key);
            $handler=Cache::get('game_'.$key);
        }
        $this->list_addr=$handler;
        return $handler;
    }
}